﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Security.AccessControl;
using System.Text;
using System.Threading;

namespace Sharp.Interprocess
{
    public abstract class BaseSharedCollection<T> : IDisposable
    {
        #region Constants
        protected const int PAGED_MEMORY_FILE_DEFAULT_SIZE = 1024 * 1024;
        protected const string SHARED_COLLECTION_MUTEX_NAME_PREFFIX = "SharedCollectionMutex_";
        protected const int LOCK_TIMEOUT_MILLISECONDS = 1000;
        protected const int RETRY_DELAY_MILLISECONDS = 10;
        protected const string SHARED_COLLECTION_COUNTER_MUTEX_NAME_SUFFIX = "_CollectionChangeCounter";
        #endregion

        #region Members

        protected readonly CollectionHeader<T> m_CollectionHeader;
        protected MemoryMappedFile m_CollectionStorageFile;
        protected readonly Mutex m_ReadWriteMutex;
        protected readonly string m_CollectionMapName;
        protected readonly Mutex m_CollectionCountMutex;
        protected readonly int m_CollectionOffset;
        protected readonly bool m_ShouldAutoExpand;

        #endregion

        #region Constructor(s)

        public BaseSharedCollection(string collectionMapName, int collectionOffset,bool shouldAutoExpand = true, int collectionMaxSize = PAGED_MEMORY_FILE_DEFAULT_SIZE)
        {
            m_CollectionMapName = collectionMapName;
            m_CollectionOffset = collectionOffset;
            m_ShouldAutoExpand = shouldAutoExpand;
            var customSecurity = new MemoryMappedFileSecurity();
            var everyoneAccessRule = new AccessRule<MemoryMappedFileRights>("EVERYONE",
                                                                MemoryMappedFileRights.ReadWrite,
                                                                AccessControlType.Allow);
            customSecurity.AddAccessRule(everyoneAccessRule);

            //TODO : add security handling for the collection implementation (replace everyone access rule)
            m_CollectionStorageFile = MemoryMappedFile.CreateOrOpen(
                collectionMapName,
                collectionMaxSize,
                MemoryMappedFileAccess.ReadWrite,
                MemoryMappedFileOptions.DelayAllocatePages,
                customSecurity,
                HandleInheritability.Inheritable);

            m_CollectionCountMutex = new Mutex(false, SHARED_COLLECTION_MUTEX_NAME_PREFFIX  + collectionMapName + collectionOffset + SHARED_COLLECTION_COUNTER_MUTEX_NAME_SUFFIX);
            m_ReadWriteMutex = new Mutex(false, SHARED_COLLECTION_MUTEX_NAME_PREFFIX + collectionMapName + collectionOffset);
            m_CollectionHeader = new CollectionHeader<T>(collectionOffset, m_CollectionStorageFile, collectionMaxSize);
            if (!m_CollectionHeader.IsFirstInstance && m_CollectionHeader.CollectionType != typeof(T))
            {
                Dispose();
                throw new ArgumentException("generic parameter type of shared collection conflicts with already existing parameter type");
            }
        }

        #endregion

        #region IDisposable Implementation
        public virtual void Dispose()
        {
            m_ReadWriteMutex.Dispose();
            m_CollectionCountMutex.Dispose();
            m_CollectionStorageFile.Dispose();
        }
        #endregion

        #region Public Members

        /// <summary>
        /// checks specific memory mapped file offset is inside the collection
        /// </summary>
        /// <param name="offset">offset to check</param>
        /// <returns>true if offset is inside the collection data, false otherwise</returns>
        public bool IsOffsetCollidesWithCollection(int offset)
        {
            return offset >= m_CollectionOffset && offset <= m_CollectionHeader.ItemLastOffset;
        }

        #endregion

        #region Helper Methods

        protected abstract List<T> GetAllItems();

        protected abstract void StoreItem(T newItem, bool checkMemoryMappedFileCapacity = true);

        protected abstract void ClearAllItems();

        protected virtual bool IsEnoughCapacityToStore(int itemSizeToStore)
        {
            var isEnoughCapacityToStore = (m_CollectionHeader.ItemLastOffset + itemSizeToStore <= m_CollectionHeader.CollectionSize);

            if (!m_ShouldAutoExpand)
            {
                throw new OutOfMemoryException("Not enough reserved capacity for shared collection items.");
            }

            return isEnoughCapacityToStore;
        }

        protected void EnlargeMemoryMappedFileSizeIfNeeded(int itemSizeToStore)
        {
            if (!IsEnoughCapacityToStore(itemSizeToStore))
            {
                try
                {
                    m_ReadWriteMutex.WaitOne();
                    var currentStoredData = GetAllItems();
                    var newCollectionMaxSize = m_CollectionHeader.CollectionSize * 3;
                    m_CollectionStorageFile.Dispose();
                    var customSecurity = new MemoryMappedFileSecurity();
                    var everyoneAccessRule = new AccessRule<MemoryMappedFileRights>("EVERYONE",
                                                                        MemoryMappedFileRights.ReadWrite,
                                                                        AccessControlType.Allow);
                    customSecurity.AddAccessRule(everyoneAccessRule);

                    m_CollectionStorageFile = MemoryMappedFile.CreateOrOpen(
                        m_CollectionMapName,
                        newCollectionMaxSize,
                        MemoryMappedFileAccess.ReadWrite,
                        MemoryMappedFileOptions.DelayAllocatePages,
                        customSecurity,
                        HandleInheritability.Inheritable);

                    m_CollectionHeader.UpdateMemoryMappedFileReference(m_CollectionStorageFile);
                    m_CollectionHeader.UpdateOnCollectionResize(newCollectionMaxSize);
                    ClearAllItems();
                    foreach (var item in currentStoredData)
                    {
                        StoreItem(item, false);
                    }
                }
                finally
                {
                    m_ReadWriteMutex.ReleaseMutex();
                }
            }
        }

        #endregion
    }
}
